/*-------------------------------------------------------------------------
 *
 * datum.c
 *	  POSTGRES Datum（抽象数据类型）操作例程。
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/utils/adt/datum.c
 *
 *-------------------------------------------------------------------------
 */

/*
 * 在这些例程的实现中，我们假设以下内容：
 *
 * A) 如果一个类型是 "byVal"，那么所有信息都存储在
 * Datum 本身（即不涉及指针！）。在这种情况下，类型的
 * 长度始终大于零且不超过 "sizeof(Datum)"
 *
 * B) 如果一个类型不是 "byVal" 并且具有固定长度（typlen > 0），
 * 则 "Datum" 总是包含指向字节流的指针。
 * 重要字节的数量始终等于 typlen。
 *
 * C) 如果一个类型不是 "byVal" 并且具有 typlen == -1，
 * 则 "Datum" 总是指向 "struct varlena"。
 * 这个 varlena 结构包含关于该特定实例的类型实际长度
 * 及其值的信息。
 *
 * D) 如果一个类型不是 "byVal" 并且具有 typlen == -2，
 * 则 "Datum" 总是指向一个以空字符结尾的 C 字符串。
 *
 * 注意，我们不特别处理 "toasted" datums；因此，将被复制或比较的
 * 是压缩数据或 toast 引用。然而，对于 expanded object 的
 * datumCopy() 做了例外，因为大多数调用者希望从 datumCopy()
 * 获取一个简单的连续（并且可以 pfree 的）结果。
 * 另请参见 datumTransfer()。
 */

#include "postgres.h"

#include "access/detoast.h"
#include "catalog/pg_type_d.h"
#include "common/hashfn.h"
#include "fmgr.h"
#include "utils/builtins.h"
#include "utils/datum.h"
#include "utils/expandeddatum.h"


/*-------------------------------------------------------------------------
 * datumGetSize
 *
 * 找到给定 datum 值的 "真实" 大小，
 * 无论它是 "按值"，以及声明的类型长度。
 * （对于 TOAST 指针 datums，这是指针 datum 的大小。）
 *
 * 这本质上是 access/tupmacs.h 中的 att_addlength_datum()
 * 宏的一个外部版本。不过我们进行了一些额外的错误检查。
 *-------------------------------------------------------------------------
 */
Size datumGetSize(Datum fc_value, bool fc_typByVal, int fc_typLen)
{
	Size		fc_size;

	if (fc_typByVal)
	{
		/* 按值传递的类型总是固定长度 */
		Assert(fc_typLen > 0 && fc_typLen <= sizeof(Datum));
		fc_size = (Size) fc_typLen;
	}
	else
	{
		if (fc_typLen > 0)
		{
			/* 固定长度的按引用传递类型 */
			fc_size = (Size) fc_typLen;
		}
		else if (fc_typLen == -1)
		{
			/* 这是一种 varlena 数据类型 */
			struct varlena *fc_s = (struct varlena *) DatumGetPointer(fc_value);

			if (!PointerIsValid(fc_s))
				ereport(ERROR,
						(errcode(ERRCODE_DATA_EXCEPTION),
						 errmsg("invalid Datum pointer")));

			fc_size = (Size) VARSIZE_ANY(fc_s);
		}
		else if (fc_typLen == -2)
		{
			/* 这是一种 cstring 数据类型 */
			char	   *fc_s = (char *) DatumGetPointer(fc_value);

			if (!PointerIsValid(fc_s))
				ereport(ERROR,
						(errcode(ERRCODE_DATA_EXCEPTION),
						 errmsg("invalid Datum pointer")));

			fc_size = (Size) (strlen(fc_s) + 1);
		}
		else
		{
			elog(ERROR, "invalid typLen: %d", fc_typLen);
			fc_size = 0;			/* 保持编译器安静 */
		}
	}

	return fc_size;
}

/*-------------------------------------------------------------------------
 * datumCopy
 *
 * 复制一个非 NULL 的 datum。
 *
 * 如果数据类型是按引用传递，内存使用 palloc() 获取。
 *
 * 如果值是对一个扩展对象的引用，我们将其平坦化到内存
 * 中，使用 palloc() 获取。我们需要复制，因为这个函数的
 * 主要用途之一是从即将被销毁的瞬时内存上下文中复制
 * 一个 datum，而扩展对象可能在一个也将消失的子上下文中。
 * 此外，许多调用者假设结果是一个单一的可 pfree 的块。
 *-------------------------------------------------------------------------
 */
Datum datumCopy(Datum fc_value, bool fc_typByVal, int fc_typLen)
{
	Datum		fc_res;

	if (fc_typByVal)
		fc_res = fc_value;
	else if (fc_typLen == -1)
	{
		/* 这是一种 varlena 数据类型 */
		struct varlena *fc_vl = (struct varlena *) DatumGetPointer(fc_value);

		if (VARATT_IS_EXTERNAL_EXPANDED(fc_vl))
		{
			/* 扁平化到调用者的内存上下文中 */
			ExpandedObjectHeader *fc_eoh = DatumGetEOHP(fc_value);
			Size		fc_resultsize;
			char	   *fc_resultptr;

			fc_resultsize = EOH_get_flat_size(fc_eoh);
			fc_resultptr = (char *) palloc(fc_resultsize);
			EOH_flatten_into(fc_eoh, (void *) fc_resultptr, fc_resultsize);
			fc_res = PointerGetDatum(fc_resultptr);
		}
		else
		{
			/* 否则，逐字复制 varlena datum */
			Size		fc_realSize;
			char	   *fc_resultptr;

			fc_realSize = (Size) VARSIZE_ANY(fc_vl);
			fc_resultptr = (char *) palloc(fc_realSize);
			memcpy(fc_resultptr, fc_vl, fc_realSize);
			fc_res = PointerGetDatum(fc_resultptr);
		}
	}
	else
	{
		/* 按引用传递，但不是 varlena，因此不是 toasted */
		Size		fc_realSize;
		char	   *fc_resultptr;

		fc_realSize = datumGetSize(fc_value, fc_typByVal, fc_typLen);

		fc_resultptr = (char *) palloc(fc_realSize);
		memcpy(fc_resultptr, DatumGetPointer(fc_value), fc_realSize);
		fc_res = PointerGetDatum(fc_resultptr);
	}
	return fc_res;
}

/*-------------------------------------------------------------------------
 * datumTransfer
 *
 * 将一个非 NULL 的 datum 转移到当前内存上下文中。
 *
 * 这与 datumCopy() 等效，除了当 datum 是一个读写
 * 指向扩展对象的指针时。在这种情况下，我们只是将对象
 * 重新指定到当前上下文，并返回其标准的 R/W 指针（以防
 * 给定的指针是一个短生命周期的瞬时指针）。
 *-------------------------------------------------------------------------
 */
Datum datumTransfer(Datum fc_value, bool fc_typByVal, int fc_typLen)
{
	if (!fc_typByVal && fc_typLen == -1 &&
		VARATT_IS_EXTERNAL_EXPANDED_RW(DatumGetPointer(fc_value)))
		fc_value = TransferExpandedObject(fc_value, CurrentMemoryContext);
	else
		fc_value = datumCopy(fc_value, fc_typByVal, fc_typLen);
	return fc_value;
}


/*-------------------------------------------------------------------------  
 * datumIsEqual  
 *  
 * 如果两个数据相等则返回true，否则返回false  
 *  
 * 注意：XXX！  
 * 我们逐字节比较这两个值。  
 * 如果同一个值有两个不同的表示（例如在反补数算术中零的表示），  
 * 这个例程将返回false。  
 * 此外，如果任一数据被“烤焦”，它可能不会给出你想要的答案。  
 *  
 * 不要试图让这个函数在处理“烤焦”数据时变得更智能，因为有些调用者可能在  
 * 中止事务的上下文中工作。  
 *-------------------------------------------------------------------------*/  
bool datumIsEqual(Datum fc_value1, Datum fc_value2, bool fc_typByVal, int fc_typLen)
{
	bool		fc_res;

	if (fc_typByVal)
	{
		/*  
		 * 只比较两个数据。注意：仅比较“len”字节无法完成这项工作，  
		 * 因为我们不知道这些字节在“Datum”内部是如何对齐的。  
		 * 我们假设任何给定的数据类型在填充Datum中的多余位时是一致的。  
		 */  
		fc_res = (fc_value1 == fc_value2);
	}
	else
	{
		Size		fc_size1,
					fc_size2;
		char	   *fc_s1,
				   *fc_s2;

		/*  
		 * 比较存储在数据中的指针指向的字节。  
		 */  
		fc_size1 = datumGetSize(fc_value1, fc_typByVal, fc_typLen);
		fc_size2 = datumGetSize(fc_value2, fc_typByVal, fc_typLen);
		if (fc_size1 != fc_size2)
			return false;
		fc_s1 = (char *) DatumGetPointer(fc_value1);
		fc_s2 = (char *) DatumGetPointer(fc_value2);
		fc_res = (memcmp(fc_s1, fc_s2, fc_size1) == 0);
	}
	return fc_res;
}

/*-------------------------------------------------------------------------  
 * datum_image_eq  
 *  
 * 基于字节图像比较两个数据的相同内容。如果两个数据相等则返回  
 * true，否则返回false。  
 *-------------------------------------------------------------------------*/  
bool datum_image_eq(Datum fc_value1, Datum fc_value2, bool fc_typByVal, int fc_typLen)
{
	Size		fc_len1,
				fc_len2;
	bool		fc_result = true;

	if (fc_typByVal)
	{
		fc_result = (fc_value1 == fc_value2);
	}
	else if (fc_typLen > 0)
	{
		fc_result = (memcmp(DatumGetPointer(fc_value1),
						 DatumGetPointer(fc_value2),
						 fc_typLen) == 0);
	}
	else if (fc_typLen == -1)
	{
		fc_len1 = toast_raw_datum_size(fc_value1);
		fc_len2 = toast_raw_datum_size(fc_value2);
		/* 如果长度不匹配，则无需去烤。 */  
		if (fc_len1 != fc_len2)
			fc_result = false;
		else
		{
			struct varlena *fc_arg1val;
			struct varlena *fc_arg2val;

			fc_arg1val = PG_DETOAST_DATUM_PACKED(fc_value1);
			fc_arg2val = PG_DETOAST_DATUM_PACKED(fc_value2);

			fc_result = (memcmp(VARDATA_ANY(fc_arg1val),
							 VARDATA_ANY(fc_arg2val),
							 fc_len1 - VARHDRSZ) == 0);

			/* 仅当这里制作了副本时才释放内存。 */  
			if ((Pointer) fc_arg1val != (Pointer) fc_value1)
				pfree(fc_arg1val);
			if ((Pointer) fc_arg2val != (Pointer) fc_value2)
				pfree(fc_arg2val);
		}
	}
	else if (fc_typLen == -2)
	{
		char	   *fc_s1,
				   *fc_s2;

		/* 比较cstring数据 */  
		fc_s1 = DatumGetCString(fc_value1);
		fc_s2 = DatumGetCString(fc_value2);
		fc_len1 = strlen(fc_s1) + 1;
		fc_len2 = strlen(fc_s2) + 1;
		if (fc_len1 != fc_len2)
			return false;
		fc_result = (memcmp(fc_s1, fc_s2, fc_len1) == 0);
	}
	else
		elog(ERROR, "unexpected typLen: %d", fc_typLen);

	return fc_result;
}

/*-------------------------------------------------------------------------  
 * datum_image_hash  
 *  
 * 基于'value'的二进制表示生成哈希值。大多数  
 * 用例希望使用与Datum类型相关的哈希函数，  
 * 然而，一些边缘情况需要基于实际位生成哈希值，而不是逻辑值。  
 *-------------------------------------------------------------------------*/  
uint32 datum_image_hash(Datum fc_value, bool fc_typByVal, int fc_typLen)
{
	Size		fc_len;
	uint32		fc_result;

	if (fc_typByVal)
		fc_result = hash_bytes((unsigned char *) &fc_value, sizeof(Datum));
	else if (fc_typLen > 0)
		fc_result = hash_bytes((unsigned char *) DatumGetPointer(fc_value), fc_typLen);
	else if (fc_typLen == -1)
	{
		struct varlena *fc_val;

		fc_len = toast_raw_datum_size(fc_value);

		fc_val = PG_DETOAST_DATUM_PACKED(fc_value);

		fc_result = hash_bytes((unsigned char *) VARDATA_ANY(fc_val), fc_len - VARHDRSZ);

		/* 仅当这里制作了副本时才释放内存。 */  
		if ((Pointer) fc_val != (Pointer) fc_value)
			pfree(fc_val);
	}
	else if (fc_typLen == -2)
	{
		char	   *fc_s;

		fc_s = DatumGetCString(fc_value);
		fc_len = strlen(fc_s) + 1;

		fc_result = hash_bytes((unsigned char *) fc_s, fc_len);
	}
	else
	{
		elog(ERROR, "unexpected typLen: %d", fc_typLen);
		fc_result = 0;				/* 保持编译器安静 */
	}

	return fc_result;
}

/*-------------------------------------------------------------------------   
 * btequalimage   
 *   
 * 通用的“equalimage”支持函数。   
 *   
 * 所有情况下其相等性函数可以安全地替换为 datum_image_eq() 的 B-Tree 操作类可以使用此作为其“equalimage”支持函数。   
 *   
 * 早期的小版本错误地将该函数与 interval_ops 关联。检测这种情况以撤销去重支持，而不需要 initdb。   
 *------------------------------------------------------------------------- */  
Datum btequalimage(PG_FUNCTION_ARGS)
{
	Oid			fc_opcintype = PG_GETARG_OID(0);

	PG_RETURN_BOOL(fc_opcintype != INTERVALOID);
}

/*-------------------------------------------------------------------------   
 * datumEstimateSpace   
 *   
 * 计算 datumSerialize 将为特定 Datum 所需的空间量。   
 *------------------------------------------------------------------------- */  
Size datumEstimateSpace(Datum fc_value, bool fc_isnull, bool fc_typByVal, int fc_typLen)
{
	Size		fc_sz = sizeof(int);

	if (!fc_isnull)
	{
		/* 无需使用 add_size，无法溢出 */  
		if (fc_typByVal)
			fc_sz += sizeof(Datum);
		else if (fc_typLen == -1 &&
				 VARATT_IS_EXTERNAL_EXPANDED(DatumGetPointer(fc_value)))
		{
			/* 扩展对象需要被扁平化，请参见下面的注释 */  
			fc_sz += EOH_get_flat_size(DatumGetEOHP(fc_value));
		}
		else
			fc_sz += datumGetSize(fc_value, fc_typByVal, fc_typLen);
	}

	return fc_sz;
}

/*-------------------------------------------------------------------------   
 * datumSerialize   
 *   
 * 将可能为 NULL 的 datum 序列化到调用者提供的存储中。   
 *   
 * 注意：“扩展”对象被扁平化，以便生成自包含的表示，但其他类型的 toast 指针则按原样传输。   
 * 这是因为该函数的预期用途是将值传递给同一数据库服务器中的另一个进程。   
 * 另一个进程无法访问此进程内存中的“扩展”对象，但我们假设它可以解引用此进程可以解引用的相同 TOAST 指针。   
 *   
 * 格式如下：首先，我们写一个 4 字节的头部字，该字是引用传递 datum 的长度，-1 表示值传递 datum，或 -2 表示 NULL。   
 * 如果值为 NULL，则不再写入任何内容。   
 * 如果它是值传递，则接下来是 sizeof(Datum) 字节。   
 * 否则，接下来是头部字指示的字节数。   
 * 调用者负责确保有足够的存储来存储将要写入的字节数；使用 datumEstimateSpace() 来查找所需的字节数。   
 * *start_address 会更新为指向后续字节。   
 *------------------------------------------------------------------------- */  
void datumSerialize(Datum fc_value, bool fc_isnull, bool fc_typByVal, int fc_typLen,
			   char **fc_start_address)
{
	ExpandedObjectHeader *fc_eoh = NULL;
	int			fc_header;

	/* 写入头部字。 */  
	if (fc_isnull)
		fc_header = -2;
	else if (fc_typByVal)
		fc_header = -1;
	else if (fc_typLen == -1 &&
			 VARATT_IS_EXTERNAL_EXPANDED(DatumGetPointer(fc_value)))
	{
		fc_eoh = DatumGetEOHP(fc_value);
		fc_header = EOH_get_flat_size(fc_eoh);
	}
	else
		fc_header = datumGetSize(fc_value, fc_typByVal, fc_typLen);
	memcpy(*fc_start_address, &fc_header, sizeof(int));
	*fc_start_address += sizeof(int);

	/* 如果不为 NULL，写入有效负载字节。 */  
	if (!fc_isnull)
	{
		if (fc_typByVal)
		{
			memcpy(*fc_start_address, &fc_value, sizeof(Datum));
			*fc_start_address += sizeof(Datum);
		}
		else if (fc_eoh)
		{
			char	   *fc_tmp;

			/*   
			 * EOH_flatten_into 期望目标地址是最大对齐的，   
			 * 所以我们不能直接存储到 *start_address。   
			 */  
			fc_tmp = (char *) palloc(fc_header);
			EOH_flatten_into(fc_eoh, (void *) fc_tmp, fc_header);
			memcpy(*fc_start_address, fc_tmp, fc_header);
			*fc_start_address += fc_header;

			/* be tidy. */
			pfree(fc_tmp);
		}
		else
		{
			memcpy(*fc_start_address, DatumGetPointer(fc_value), fc_header);
			*fc_start_address += fc_header;
		}
	}
}

/*-------------------------------------------------------------------------   
 * datumRestore   
 *   
 * 恢复之前由 datumSerialize 序列化的可能为 NULL 的 datum。   
 * *start_address 会根据消耗的字节数进行更新。   
 *------------------------------------------------------------------------- */  
Datum datumRestore(char **fc_start_address, bool *fc_isnull)
{
	int			fc_header;
	void	   *fc_d;

	/* 读取头部字。 */  
	memcpy(&fc_header, *fc_start_address, sizeof(int));
	*fc_start_address += sizeof(int);

	/* 如果此 datum 为 NULL，我们可以在此停止。 */  
	if (fc_header == -2)
	{
		*fc_isnull = true;
		return (Datum) 0;
	}

	/* 好的，datum 不是 NULL。 */  
	*fc_isnull = false;

	/* 如果此 datum 是值传递，则接下来是 sizeof(Datum) 字节。 */  
	if (fc_header == -1)
	{
		Datum		fc_val;

		memcpy(&fc_val, *fc_start_address, sizeof(Datum));
		*fc_start_address += sizeof(Datum);
		return fc_val;
	}

	/* 引用传递情况；复制指示的字节数。 */  
	Assert(fc_header > 0);
	fc_d = palloc(fc_header);
	memcpy(fc_d, *fc_start_address, fc_header);
	*fc_start_address += fc_header;
	return PointerGetDatum(fc_d);
}
